﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using MonoShapelib;
using System.Drawing;
using System.Net;
using System.Threading;

namespace SHPBaidu
{
    public class SHPWriter
    {

        /// <summary>
        /// 坐标转换
        /// </summary>
        /// <param name="inSHP"></param>
        public void Test(string inSHP)
        {
            // 先读取所有的节点
            SHPHandle hSHP = SHPHandle.Open(inSHP, "r+b");
            List<PointF> points = GetVertices(hSHP);
            
            // 将点坐标写入SHP
            List<PointF> baidupoints = Transform(points);
            SetVertices(hSHP, points, baidupoints);
            hSHP.Close();
        }
        /// <summary>
        /// 读取所有元素的所有节点
        /// </summary>
        /// <param name="hSHP"></param>
        /// <returns></returns>
        private List<PointF> GetVertices(SHPHandle hSHP)
        {
            List<PointF> points = new List<PointF>();
            SHPT nShapeType;
            int nEntities;
            double[] adfMinBound = new double[4], adfMaxBound = new double[4];
            hSHP.GetInfo(out nEntities, out nShapeType, adfMinBound, adfMaxBound);
            for (int i = 0; i < nEntities; i++) {
                SHPObject psShape = hSHP.ReadObject(i);
                for (int j = 0; j < psShape.nVertices; j++) {
                    points.Add(new PointF((float)psShape.padfX[j], (float)psShape.padfY[j]));
                }
            }
            return points;
        }
        /// <summary>
        /// 将坐标写入SHP
        /// </summary>
        /// <param name="hSHP"></param>
        /// <param name="oldpoints"></param>
        /// <param name="newpoints"></param>
        private void SetVertices(SHPHandle hSHP, List<PointF> oldpoints, List<PointF> newpoints)
        {
            SHPT nShapeType;
            int nEntities;
            double[] adfMinBound = new double[4], adfMaxBound = new double[4];
            hSHP.GetInfo(out nEntities, out nShapeType, adfMinBound, adfMaxBound);
            int num = 0;
            for (int i = 0; i < nEntities; i++) {
                SHPObject psShape = hSHP.ReadObject(i);
                for (int j = 0; j < psShape.nVertices; j++) {
                    PointF point = new PointF((float)psShape.padfX[j], (float)psShape.padfY[j]);
                    PointF oldpoint = oldpoints[num];
                    PointF newpoint = newpoints[num];
                    num++;
                    if (point != oldpoint) continue;
                    psShape.padfX[j] = newpoint.X;
                    psShape.padfY[j] = newpoint.Y;
                }
                hSHP.WriteObject(psShape.nShapeId, psShape);
                psShape = null;
            }
        }

        # region 调用百度API把每一个点转
        private ManualResetEvent eventX = new ManualResetEvent(false);      // 锁
        private int maxCount = 0;                                           // 任务总数
        private int icount = 0;
        /// <summary>
        /// GPS坐标点转百度坐标
        /// </summary>
        /// <param name="points">原始GPS坐标</param>
        /// <returns>百度坐标</returns>
        private List<PointF> Transform(List<PointF> points)
        {
            #region 旧的API 最多一次只能转20个
            // http://api.map.baidu.com/ag/coord/convert?from=2&to=4&x=116.254615&y=29.814479
            // http://api.map.baidu.com/ag/coord/convert?from=0&to=4&x=116.501429&y=39.991705
            // http://developer.baidu.com/map/uri-intro.htm
            // http://developer.baidu.com/map/changeposition.htm
            // http://developer.baidu.com/map/index.php?title=webapi/guide/changeposition
            // http://api.map.baidu.com/geoconv/v1/?coords=114.21892734521,29.575429778924;114.21892734521,29.575429778924&ak=E4805d16520de693a3fe707cdc962045&output=json
            // http://developer.baidu.com/map/index.php?title=webapi/guide/changeposition
            //将其他坐标系转换到百度坐标系。接口样例如下：
            //http://api.map.baidu.com/ag/coord/convert?from=2&to=4&x=116.254615&y=29.814476
            //参数说明：
            //x/y：经纬度坐标
            //from/to：决定转换效果，具体参 数天填充如下：
            //from=2表示谷歌
            //from=0表示gps
            //to=4 表示百度
            //坐标系	参数
            //WGS-84坐标系（GPS）	from=0,to=4
            //GCJ-02坐标系（谷歌、soso、搜狗)	from=2,to=4
            //51地图坐标系	(需要先将51地图坐标除10000)from=2,to=4
            //mapbar坐标系	需要先调用后面的转换算法，得到WGS84坐标，再使用：from=0,to=4
            // http://api.map.baidu.com/ag/coord/convert?from=2&to=4&mode=1&x=116.3786889372559,116.38632786853032,116.39534009082035,116.40624058825688,116.41413701159672&y=39.90762965106183,39.90795884517671,39.907432133833574,39.90789300648029,39.90795884517671
            #endregion

            bool online = false;

            if (online){
                // 在线
                eventX = new ManualResetEvent(false);
                // 新API 坐标转换服务无日请求次数限制。 坐标转换服务每次最多支持100个坐标点的转换且并发数为1000次/秒
                List<PointF> outpoints = new List<PointF>();
                for (int i = 0; i < points.Count; i++) outpoints.Add(new PointF(0, 0));
                ThreadPool.SetMaxThreads(100, 100);
                maxCount = points.Count / 100 + (points.Count % 100 == 0 ? 0 : 1);      // 任务总数
                icount = 0;

                for (int i = 0; i < points.Count / 100; i++) {
                    object state = new object[] { points, outpoints, i * 100, 100 };
                    ThreadPool.QueueUserWorkItem(new WaitCallback(TransformThread), state);
                }
                if (points.Count % 100 > 0) {
                    int count = points.Count % 100;
                    int index = points.Count - 1 - count;
                    object state = new object[] { points, outpoints, index, count };
                    ThreadPool.QueueUserWorkItem(new WaitCallback(TransformThread), state);
                }
                eventX.WaitOne(Timeout.Infinite, true);     // WaitHandle.WaitAll(waitHandles);
                return outpoints;
            }
            else{
                // 离线
                BaiduConvert baiduConvert = new BaiduConvert();
                List<PointF> outpoints = new List<PointF>();
                foreach (PointF point in points) {
                    double lng = point.X;
                    double lat = point.Y;
                    double[] gg = baiduConvert.GPS2Mars(lat, lng);
                    double gglat = gg[0];
                    double gglng = gg[1];
                    double[] bd = baiduConvert.BD_encrypt(gglat, gglng);
                    double bdlat = bd[0];
                    double bdlng = bd[1];
                    outpoints.Add(new PointF((float)bdlng, (float)bdlat));
                }
                return outpoints;
            }
        }
        /// <summary>
        /// 处理线程
        /// </summary>
        /// <param name="args"></param>
        private void TransformThread(object state)
        {
            object[] args = state as object[];
            List<PointF> points = args[0] as List<PointF>;
            List<PointF> outpoints = args[1] as List<PointF>;
            int index = (int)args[2];
            int count = (int)args[3];

            // 尝试4次
            bool success = false;
            if (success == false) success = TransformPoints(points, outpoints, index, count);
            if (success == false) success = TransformPoints(points, outpoints, index, count);
            if (success == false) success = TransformPoints(points, outpoints, index, count);
            if (success == false) success = TransformPoints(points, outpoints, index, count);
            if (success == false)
            { throw new Exception("error ...."); }

            //Console.WriteLine("index:{0} count:{1}", index, count);
            Interlocked.Increment(ref icount);
            Console.WriteLine("{0}/{1}", icount, maxCount);
            if (icount == maxCount) {
                //将事件状态设置为终止状态，允许一个或多个等待线程继续。
                eventX.Set();
            }
        }
        /// <summary>
        /// 点转换
        /// </summary>
        /// <param name="points">原始点列表</param>
        /// <param name="outpoints">结果列表</param>
        /// <param name="index">开始索引</param>
        /// <param name="count">处理长度</param>
        /// <returns></returns>
        private bool TransformPoints(List<PointF> points, List<PointF> outpoints, int index, int count)
        {
            try {
                string from = "3";
                string to = "5";
                string ak = "M034idvG2MFv63UnDBvCHWq9";
                string url = "http://api.map.baidu.com/geoconv/v1/?from={0}&to={1}&ak={2}&coords={3}";
                string coords = "";
                List<PointF> childs = points.GetRange(index, count);
                foreach (PointF point in childs) {
                    if (coords == "") coords = string.Format("{0},{1}", point.X, point.Y);
                    else coords += string.Format(";{0},{1}", point.X, point.Y);
                }

                // 
                string html = GetHtml(string.Format(url, from, to, ak, coords), Encoding.UTF8);
                Dictionary<string, object> json = new System.Web.Script.Serialization.JavaScriptSerializer().DeserializeObject(html) as Dictionary<string, object>;
                if (json == null || json["status"].ToString() != "0") return false;
                object[] result = json["result"] as object[];
                foreach (var item in result) {
                    Dictionary<string, object> coord = item as Dictionary<string, object>;
                    float x = (float)Convert.ToDouble(coord["x"]);
                    float y = (float)Convert.ToDouble(coord["y"]);
                    outpoints[index++] = new PointF(x, y);
                }
                return true;
            }
            catch (Exception ex) { return false; }
        }
        /// <summary>  
        ///   
        /// </summary>  
        /// <param name="url">网页地址</param>  
        /// <param name="encoding">编码方式</param>  
        /// <returns></returns>  
        private string GetHtml(string url, Encoding encoding)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Timeout = 5000;
            request.Method = "Get";
            WebResponse response = request.GetResponse();
            Stream responseStream = response.GetResponseStream();
            StreamReader reader = new StreamReader(responseStream, encoding);
            String html = reader.ReadToEnd();
            response.Close();
            return html;
        }
        #endregion

    }
}
